package com.tmsps.common.bean;

import com.alibaba.fastjson.JSONObject;
import com.tmsps.common.exception.ServiceException;
import com.tmsps.common.util.AppClassUtil;
import com.tmsps.common.util.result.ResponseCodeEnum;
import com.tmsps.ne4spring.base.BaseMySQLService;
import com.tmsps.ne4spring.orm.ClassUtil;
import com.tmsps.ne4spring.orm.model.DataModel;
import com.tmsps.ne4spring.page.Page;
import com.tmsps.ne4spring.page.PageVo;
import com.tmsps.ne4spring.utils.ChkUtil;
import com.tmsps.ne4spring.utils.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;

/**
 * @author jiangtao
 */
@Slf4j
public abstract class BaseService<T extends DataModel> {

    @Autowired
    @Qualifier("baseMySQLService")
    protected BaseMySQLService baseService;

    /**
     * 泛型的Class
     */
    private Class<T> entityClass;

    public BaseService(){
        Type[] types = null;

        Class<?> clazz = getClass();
        // 如果当前对象实例是 BaseService 则直接返回
        if(clazz.equals(BaseService.class)){
            return;
        }

        // 递归获取 baseService 上的泛型
        while (!BaseService.class.equals(clazz.getSuperclass())){
            clazz = clazz.getSuperclass();
        }

        types = AppClassUtil.getSupperClassActualType(clazz);
        if(null == types || types.length < 1){
            throw new RuntimeException("请正确配置BaseService泛型");
        }
        this.entityClass = (Class<T>) types[0];
    }

    public boolean validFieldValueUnique(String validField,String validValue,String pkValue) {
        return this.validFieldValueUnique(entityClass,validField,Constant.STATUS_NORMAL,validValue,pkValue);
    }

    public boolean validFieldValueUnique(Class<? extends DataModel> source,String validField,String validValue,String pkValue) {
        return this.validFieldValueUnique(source,validField,Constant.STATUS_NORMAL,validValue,pkValue);
    }

    /**
     * 验证所给字段值唯一性
     *
     * @author geek
     * @param source		类
     * @param validField	字段名
     * @param validValue	字段值
     * @param status        数据状态
     * @param pkValue		主键值
     * @return boolean		true 可以使用，false 已存在重复值
     */
    public boolean validFieldValueUnique(Class<? extends DataModel> source, String validField, Integer status, String validValue, String pkValue) {
        String sql = String.format("SELECT * FROM %s WHERE `status`>=? AND %s=?", ClassUtil.getTableName(source),validField);
        DataModel obj = baseService.findObj(source, sql, status, validValue);
        //验证添加
        if(obj == null) {
            return true;
        }
        //验证编辑(1.20 把obj.getPK()转成字符串)
        if(StrUtil.notBlank(pkValue) && pkValue.equals(obj.getPK().toString())) {
            return true;
        }
        return false;
    }

    /**
     * 自定义列表的sql语句
     * select 语句使用 sqlAppend()
     * from 语句使用 fromAppend()
     *
     * @param srh 查询数据条件
     * @return 返回ParamBean
     */
    public abstract ParamBean getFindPageParamBean(JSONObject srh);

    /**
     * 在保存数据前设置数据的默认属性
     *  例：status、created、updateTime
     *
     * @param model 数据对象
     */
    public void setModelDefaultValue(T model){
        // 子类可以履写方法，设置默认属性值
    }

    /**
     * 只对子类开放，方便开发
     * 提供查询sql，只返回列表中第一个元素
     *
     * @param pb 封装了sql信息的对象
     * @return 返回查询结果
     */
    protected T findOneByParamBean(ParamBean pb) {
        List<T> models = baseService.findList(entityClass, pb.getSql() + pb.getFrom(), pb.getParams());
        if(ChkUtil.isNull(models)){
            return null;
        }
        return models.get(0);
    }

    /**
     * 查询并返回列表数据
     *
     * @param pb 封装了sql信息的对象
     * @return 返回列表数据
     */
    protected List<T> findListByParamBean(ParamBean pb) {
        return baseService.findList(entityClass, pb.getSql() + pb.getFrom(), pb.getParams());
    }

    /**
     * 查询分页数据
     *
     * @param page 分页数据
     * @param pb sql数据
     * @return 分页结果
     */
    protected PageVo<T> findPageByParamBean(Page page, ParamBean pb) {
        PageVo<T> pageVo = new PageVo<>();
        pageVo.setPageNumber(page.getPageNumber());
        pageVo.setPageSize(page.getPageSize());

        return baseService.pageinate(pageVo, pb.getSql(), pb.getFrom(), pb.getParams(), entityClass);
    }

    /**
     * 查询分页数据
     *
     * @param pageNumber 当前页码 从1开始
     * @param pageSize 页面容量
     * @param pb sql数据
     * @return 分页结果
     */
    protected PageVo<T> findPageByParamBean(int pageNumber, int pageSize, ParamBean pb) {
        PageVo<T> pageVo = new PageVo<>();
        pageVo.setPageNumber(pageNumber);
        pageVo.setPageSize(pageSize);
        return baseService.pageinate(pageVo, pb.getSql(), pb.getFrom(), pb.getParams(), entityClass);
    }

    /**
     * 新增一条数据，生成insert语句时，为null字段不会生成
     *
     * @param model 数据
     * @return 返回更新行数
     * @see BaseMySQLService#saveTemplateObj(com.tmsps.ne4spring.orm.model.DataModel)
     */
    public int saveTemplateObj(T model){
        setModelDefaultValue(model);
        return baseService.saveTemplateObj(model);
    }

    /**
     * 新增一条数据，生成insert语句时，会将数据库字段置为null
     * 数据库设置了默认值时，不建议使用此方法
     *
     * @param model 数据
     * @return 返回更新行数
     * @see BaseMySQLService#saveObj(com.tmsps.ne4spring.orm.model.DataModel, boolean)
     */
    public int save(T model){
        setModelDefaultValue(model);
        return baseService.saveObj(model);
    }

    /**
     * 根据id查询一条数据
     *
     * @param id 数据主键
     * @return 数据
     * @see BaseService#modelHasDelete(com.tmsps.ne4spring.orm.model.DataModel)
     */
    public T findOne(String id){
        T model = baseService.findById(id, entityClass);
        if(ChkUtil.isNull(model) || modelHasDelete(model)){
            return null;
        }
        return model;
    }

    /**
     * 获取列表分页数据
     * 请实现getFindPageParamBean方法协助完成查询功能
     *
     * @param pageNumber 当前页码，从1开始
     * @param pageSize 页面容量
     * @param srh 查询参数
     * @return 结果数据
     * @see BaseService#getFindPageParamBean(com.alibaba.fastjson.JSONObject)
     */
    public PageVo<T> findPage(int pageNumber, int pageSize, JSONObject srh) {
        ParamBean pb = getFindPageParamBean(srh);
        return findPageByParamBean(pageNumber, pageSize, pb);
    }

    /**
     * 获取列表分页数据
     * 请实现getFindPageParamBean方法协助完成查询功能
     *
     * @param page 分页参数
     * @param srh 查询参数
     * @return 结果数据
     * @see BaseService#getFindPageParamBean(com.alibaba.fastjson.JSONObject)
     */
    public PageVo<T> findPage(Page page, JSONObject srh) {
        return findPage(page.getPageNumber(), page.getPageSize(), srh);
    }

    /**
     * 获取列表数据
     * 请实现getFindPageParamBean方法协助完成查询功能
     *
     * @param srh 查询参数
     * @return 结果数据
     * @see BaseService#getFindPageParamBean(com.alibaba.fastjson.JSONObject)
     */
    public List<T> findList(JSONObject srh) {
        ParamBean pb = getFindPageParamBean(srh);
        return findListByParamBean(pb);
    }

    /**
     * 增量更新数据
     *
     * @param model 数据
     * @return 结果
     */
    public int updateChangeObj(T model){
        return baseService.updateChangeObj(model);
    }

    /**
     * 删除数据，如果数据不存在会抛出异常，以便自定义返回信息
     * 请实现notFoundModelException完成删除功能
     *
     * @param id 数据Id
     * @return 更新数据的行数
     */
    public int delete(String id){
        final T modelDb = findOne(id);
        if(ChkUtil.isNull(modelDb)){
            log.error("删除数据失败:[{}]-查询数据为空", id);
            throw new ServiceException(ResponseCodeEnum.EX_USER_REG_RESOURCE);
        };
        AppClassUtil.setDeleteStatus(modelDb);
        return updateChangeObj(modelDb);
    }

    /**
     * 删除数据
     *
     * @param model 数据实体
     * @return 更新数据行数
     */
    public int delete(T model){
        return delete(String.valueOf(model.getPK()));
    }

    /**
     * 自定义数据已经删除功能
     *
     * @param model 实体bean
     * @return true 表示数据已经删除
     */
    private boolean modelHasDelete(T model) {
        Field deleteField = AppClassUtil.getDeleteField(model);
        int deleteStatus = AppClassUtil.getDeleteFieldVal(deleteField);
        Object modelDeleteStatus = AppClassUtil.getClassVal(deleteField, model);
        return Objects.equals(modelDeleteStatus, deleteStatus);
    };

}
